package org.jeecg.common.util.sm.utils;

import lombok.val;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.cms.*;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
import org.bouncycastle.asn1.x509.TBSCertificate;
import org.bouncycastle.cms.CMSEnvelopedData;
import org.bouncycastle.util.encoders.Base64;
import org.jeecg.common.util.sm.bean.KeyAndIV;

import javax.crypto.Cipher;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.PublicKey;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;

public class RSAUtils {

    public static String Des3Aly = "DESEDE/CBC/PKCS7Padding";
    public static String RSA_pubKey_encrypt_oid = "1.2.840.113549.1.1.1";
    public static String RSAData_oid = "1.2.840.113549.1.7.1";
    public static String RSAEnvelopedData_oid = "1.2.840.113549.1.7.3";
    public static String Des3_CBC_oid = "1.2.840.113549.3.7";
    public static String SubjectKey_oid = "2.5.29.14";

    public static String envelopData(X509Certificate cert, String content) {

        try {
            //生成对称密钥key和IV
            KeyAndIV keyAndIV = KeyAndIV.generateKeyAndIV(8, 24);

            PublicKey publicKey = cert.getPublicKey();

            Cipher cipher = Cipher.getInstance("RSA");
            //指定 Cipher 对象的模式为加密模式  使用前面生成的 pubKey 对象来初始化 Cipher 对象。
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            //使用 Cipher 对消息进行加密。
            byte[] encryptedBytes = cipher.doFinal(keyAndIV.getKey());

            ASN1OctetString sm2CipherObject = new BEROctetString(encryptedBytes);
            //使用对称密钥对原文进行加密
            byte[] sm4EncryptCBCByte = SM4KeyUtils.getSm4Encrypt(Des3Aly, content, keyAndIV);
            ASN1OctetString encryptedOctet = new DEROctetString(sm4EncryptCBCByte);

            EncryptedContentInfo encryptedContentInfo = new EncryptedContentInfo(new ASN1ObjectIdentifier(RSAData_oid),
                    new AlgorithmIdentifier(new ASN1ObjectIdentifier(Des3_CBC_oid), new DEROctetString(keyAndIV.getIv())),
                    encryptedOctet);
            //获取subjectKeyIdentifier 内容
            RecipientIdentifier recipientIdentifier = getRecipientIdentifier(cert);
            //构造接收者信封结构体
            ASN1EncodableVector recipientInfos = getAsn1EncodableVector(recipientIdentifier, sm2CipherObject);

            EnvelopedData envelopedData = new EnvelopedData(null, new DERSet(recipientInfos), encryptedContentInfo, ASN1Set.getInstance(null));
            ContentInfo contentInfo = new ContentInfo(new ASN1ObjectIdentifier(RSAEnvelopedData_oid), envelopedData);
            CMSEnvelopedData cmsEnvelopedData = new CMSEnvelopedData(contentInfo);
            return Base64.toBase64String(cmsEnvelopedData.getEncoded());
        } catch (Exception exception) {
            exception.printStackTrace();
            return "";
        }
    }

    private static ASN1EncodableVector getAsn1EncodableVector(RecipientIdentifier recipientIdentifier, ASN1OctetString sm2CipherObject) {
        KeyTransRecipientInfo keyTransRecipientInfo =
                new KeyTransRecipientInfo(recipientIdentifier,
                        new AlgorithmIdentifier(new ASN1ObjectIdentifier(RSA_pubKey_encrypt_oid), DERNull.INSTANCE),
                        sm2CipherObject);
        RecipientInfo receiverCerts = new RecipientInfo(keyTransRecipientInfo);
        ASN1EncodableVector recipientInfos = new ASN1EncodableVector();
        recipientInfos.add(receiverCerts);
        return recipientInfos;
    }

    /**
     * 从证书中解析subjectKeyIdentifier
     */
    private static RecipientIdentifier getRecipientIdentifier(X509Certificate cert) throws CertificateEncodingException, IOException {
        val instance = TBSCertificate.getInstance(cert.getTBSCertificate());
        val extensions = instance.getExtensions();
        val extension = extensions.getExtension(new ASN1ObjectIdentifier(SubjectKey_oid));
        val octets = extension.getExtnValue().getOctets();
        ASN1InputStream asn1InputStream = new ASN1InputStream(new ByteArrayInputStream(octets));
        ASN1Object asn1Object = asn1InputStream.readObject();
        SubjectKeyIdentifier subjectKeyIdentifier = SubjectKeyIdentifier.getInstance(asn1Object);
        DEROctetString derOctetString = new DEROctetString(subjectKeyIdentifier.getKeyIdentifier());
        return new RecipientIdentifier(derOctetString);
    }
}
